package demo8

import (
	"fmt"
	"sync"
)

/*
	互斥锁（sync.Mutex）和读写互斥锁（sync.RWMutex）
		1. sync 包提供了两种锁类型：sync.Mutex 和 sync.RWMutex。
		2. Mutex 是最简单的一种锁类型，同时也比较暴力，当一个 goroutine 获得了 Mutex 后，
			其他 goroutine 就只能乖乖等到这个 goroutine 释放该 Mutex。
		3. RWMutex 相对友好些，是经典的单写多读模型。
			在读锁占用的情况下，会阻止写，但不阻止读，也就是多个 goroutine 可同时获取读锁（调用 RLock() 方法；
			而写锁（调用 Lock() 方法）会阻止任何其他 goroutine（无论读和写）进来，整个锁相当于由该 goroutine 独占。
			从 RWMutex 的实现看，RWMutex 类型其实组合了 Mutex：
		type RWMutex struct {
			w Mutex
			writerSem uint32
			readerSem uint32
			readerCount int32
			readerWait int32
		}
*/

var (
	// 逻辑中使用的某个变量, 无论是包级的变量还是结构体成员字段，都可以。
	count int

	// 与变量对应的使用互斥锁, 一般情况下，建议将互斥锁的粒度设置得越小越好，降低因为共享访问时等待的时间
	countGuard sync.Mutex

	// 读写互斥锁
	countRWGuard sync.RWMutex
)

// 获取 count 值的函数封装，通过这个函数可以并发安全的访问变量 count
func getCount() int {
	// 尝试对 countGuard 互斥量进行加锁。
	// 一旦 countGuard 发生加锁，如果另外一个 goroutine 尝试继续加锁时将会发生阻塞，直到这个 countGuard 被解锁。
	countGuard.Lock()

	// 在函数退出时解除锁定
	defer countGuard.Unlock()

	return count
}

// 使用 RWLock 锁
// 获取 count 的过程是一个读取 count 数据的过程，适用于读写互斥锁。
func getCount2() int {
	// 锁定
	// 把 countGuard.Lock() 换做 countGuard.RLock()，将读写互斥锁标记为读状态
	countRWGuard.RLock()

	// 在函数退出时解除锁定
	// 如果此时另外一个 goroutine 并发访问了 countGuard，同时也调用了 countGuard.RLock() 时，并不会发生阻塞。
	defer countRWGuard.RUnlock()

	return count
}

// 设置 count 值
// 同样使用 countGuard 进行加锁、解锁操作，保证修改 count 值的过程是一个原子过程，不会发生并发访问冲突。
func setCount(c int) {
	countGuard.Lock()
	count = c
	countGuard.Unlock()
}

// TestDemo816 互斥锁（sync.Mutex）和读写互斥锁（sync.RWMutex）
func TestDemo816() {
	// 进行并发安全的设置
	setCount(1)

	// 可以进行并发安全的读取
	fmt.Println(getCount2())
}
